﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;

namespace CFScript
{
    /// <summary>
    /// TODO: Update summary.
    /// </summary>
    public abstract class Instruction:RunnableItem
    {
        protected string name;
        protected Dictionary<string, Parameter> parameters;
        protected string[] parameterNames;
        protected bool[] parameterRequired;
        protected bool[] parameterExpandable;
        protected bool hasNestedInstructions = false;
        protected List<Instruction> nestedInstructions;
        protected bool hasInnerText = false;
        protected string innerText;


        public abstract InstructionResult Execute(CFScript context);

        public Dictionary<string, Parameter> Parameters
        {
            get { return this.parameters; }
            set { this.parameters = value; }
        }

        public static Instruction InstructionFactory(XmlElement node)
        {
            Instruction result = ResolveInstruction(node);

            result.ValidateParameters();

            return result;
        }

        private static Instruction ResolveInstruction(XmlElement node)
        {
            Instruction result = null;

            switch(node.Name)
            {
                case "set":
                    result = new Tasks.Set();
                    break;
                case "echo":
                    result = new Tasks.Echo();
                    break;
                case "if":
                    result = new Control.If();
                    break;
                case "delete":
                    result = new Tasks.Delete();
                    break;
                case "copy":
                    result = new Tasks.Copy();
                    break;
                case "write":
                    result = new Tasks.Write();
                    break;
                case "execute":
                    result = new Tasks.Start();
                    break;
                case "kill":
                    result = new Tasks.Kill();
                    break;
                case "close":
                    result = new Tasks.Close();
                    break;
            }

            if (result != null)
            {
                for (int i = 0; i < result.parameterNames.Length; i++)
                {
                    string pName = result.parameterNames[i];
                    if (node.HasAttribute(pName))
                    {
                        result.parameters[pName] = new Parameter(pName, node.GetAttribute(pName), result.parameterExpandable[i], result.parameterRequired[i]);
                    }
                }

                if (node.HasAttribute("onError"))
                {
                    string action = node.GetAttribute("onError");
                    result.options.OnError = (OnErrorAction)Enum.Parse(typeof(OnErrorAction), action, true);
                }

                if (result.hasInnerText)
                {
                    result.innerText = node.InnerText.Trim();
                }

                if (result.hasNestedInstructions && node.HasChildNodes)
                {
                    result.nestedInstructions = new List<Instruction>();

                    foreach (XmlElement subnode in node.ChildNodes)
                    {
                        result.nestedInstructions.Add(Instruction.InstructionFactory(subnode));
                    }
                }
            }
            else
            {
                throw new ScriptParseException(string.Format("Node instruction {0} not supported", node.Name));
            }

            return result;
        }

        public bool ValidateParameters()
        {
            bool result = true;

            if(this.parameterNames != null && this.parameterNames.Length >0)
            {
                for (int i = 0; i < this.parameterNames.Length; i++)
                {
                    result &= this.parameters.ContainsKey(this.parameterNames[i]) || !this.parameterRequired[i];
                    if (!result)
                    {
                        throw new MissingRequiredParameterException(string.Format("Parameter {0} is required.", this.parameterNames[i]));
                    }
                }
            }
            return result;
        }
    }
}
